// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. See the AUTHORS file for names of contributors.
//
// Logger implementation that can be shared by all environments
// where enough posix functionality is available.

#ifndef STORAGE_LEVELDB_UTIL_POSIX_LOGGER_H_
#define STORAGE_LEVELDB_UTIL_POSIX_LOGGER_H_

#include <sys/time.h>

#include <cassert>
#include <cstdarg>
#include <cstdio>
#include <ctime>
#include <sstream>
#include <thread>

#include "leveldb/env.h"

namespace leveldb {

class PosixLogger final : public Logger {
public:
    // Creates a logger that writes to the given file.
    //
    // The PosixLogger instance takes ownership of the file handle.
    explicit PosixLogger(std::FILE* fp) : fp_(fp) { assert(fp != nullptr); }

    ~PosixLogger() override { std::fclose(fp_); }

    void Logv(const char* format, std::va_list arguments) override {
        // Record the time as close to the Logv() call as possible.
        struct ::timeval now_timeval;
        ::gettimeofday(&now_timeval, nullptr);
        const std::time_t now_seconds = now_timeval.tv_sec;
        struct std::tm now_components;
        ::localtime_r(&now_seconds, &now_components);

        // Record the thread ID.
        constexpr const int kMaxThreadIdSize = 32;
        std::ostringstream thread_stream;
        thread_stream << std::this_thread::get_id();
        std::string thread_id = thread_stream.str();
        if (thread_id.size() > kMaxThreadIdSize) {
            thread_id.resize(kMaxThreadIdSize);
        }

        // We first attempt to print into a stack-allocated buffer. If this attempt
        // fails, we make a second attempt with a dynamically allocated buffer.
        constexpr const int kStackBufferSize = 512;
        char stack_buffer[kStackBufferSize];
        static_assert(sizeof(stack_buffer) == static_cast<size_t>(kStackBufferSize),
                      "sizeof(char) is expected to be 1 in C++");
        
        int dynamic_buffer_size = 0; // Computed in the first iteration.
        for (int iteration = 0; iteration < 2; ++iteration) {
            const int buffer_size = 
                (iteration == 0) ? kStackBufferSize : dynamic_buffer_size;
            char* const buffer =
                (iteration == 0) ? stack_buffer : new char[dynamic_buffer_size];

            // Print the header into the buffer.
            int buffer_offset = std::snprintf(
                buffer, buffer_size, "%04d/%02d/%02d-%02d:%02d:%02d.%06d %s ",
                now_components.tm_year + 1900, now_components.tm_mon + 1,
                now_components.tm_mday, now_components.tm_hour, now_components.tm_min,
                now_components.tm_sec, static_cast<int>(now_timeval.tv_usec),
                thread_id.c_str());
            
            // The header can be at most 28 characters (10 date + 15 time +
            // 3 delimiters) plus the thread ID, which should fit comfortably into the
            // static buffer.
            assert(buffer_offset <= 28 + kMaxThreadIdSize);
            static_assert(28 + kMaxThreadIdSize < kStackBufferSize,
                          "stack-allocated buffer may not fit the message header");
            assert(buffer_offset < buffer_size);

            // Print the message into the buffer.
            std::va_list arguments_copy;
            va_copy(arguments_copy, arguments);
            buffer_offset +=
                std::vsnprintf(buffer + buffer_offset, buffer_size - buffer_offset,
                               format, arguments_copy);
            va_end(arguments_copy);

            // The code below may append a newline at the end of the buffer,which
            // requires an extra cahracter.
            if (buffer_offset >= buffer_size - 1) {
                // The message did not fit into the buffer.
                if (iteration == 0) {
                    // Re-run the loop and use a dynamically-allocated buffer. The buffer
                    // will be large enough for the log message, an extra newline and a
                    // null terminator.
                    dynamic_buffer_size = buffer_offset + 2;
                    continue;
                }

                // The dynamically-allocated buffer was incorrectly sized. This should
                // not happen, assuming a correct implementation of std::(v)snprintf.
                // Fail in tests, recover by truncating the log message in production.
                assert(false);
                buffer_offset = buffer_size - 1;
            }

            // Add a newline if necessary.
            if (buffer[buffer_offset - 1] != '\n') {
                buffer[buffer_offset] = '\n';
                ++buffer_offset;
            }

            assert(buffer_offset <= buffer_size);
            std::fwrite(buffer, 1, buffer_offset, fp_);
            std::fflush(fp_);

            if (iteration != 0) {
                delete[] buffer;
            }
            break;
        }
    }

private:
    std::FILE* const fp_;
};

}  // namespace leveldb

#endif  // STORAGE_LEVELDB_UTIL_POSIX_LOGGER_H_
